package com.sysd.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sysd.bean.entity.User;
import com.sysd.bean.request.LoginRequest;
import com.sysd.bean.response.UserResponse;
import com.sysd.constant.UserRedisKeyConstant;
import com.sysd.context.UserContext;
import com.sysd.entity.CodeMsg;
import com.sysd.entity.LoginUser;
import com.sysd.exception.ServiceException;
import com.sysd.mapper.UserMapper;
import com.sysd.service.ConcernService;
import com.sysd.service.UserService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author bsh
 * @since 2025-06-03
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private final UserMapper userMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final ConcernService concernService;

    /**
     * 登入获取自己的信息这里的话支持多端登入
     *
     * @param loginRequest 登入请求
     * @return 用户信息
     */
    @Override
    public UserResponse toLogin(LoginRequest loginRequest) {
        if (!Validator.isMobile(loginRequest.getPhone())) {
            throw new ServiceException(CodeMsg.LOGIN_ERROR);
        }
        //先获取一下用户信息
        UserResponse u = userMapper.toLogin(loginRequest.getPhone());
        if (null == u) {
            return this.registerUser(new User(loginRequest.getPhone()));
        }
        //这里用的uuid来当token，后面你可以用jwt或者其他的方式来生成token
        String uuid = UUID.randomUUID().toString();
        u.setToken(uuid);
        u.setFans(this.fansCount(u.getUserId()));
        u.setConcern(this.fansCount(u.getUserId()));
        //用户信息放入redis
        stringRedisTemplate.opsForValue().set(String.format(UserRedisKeyConstant.USER_LOGIN_KEY, uuid), JSON.toJSONString(u));
        //⑦天不续约就过期
        stringRedisTemplate.expire(String.format(UserRedisKeyConstant.USER_LOGIN_KEY, uuid), 7L, TimeUnit.DAYS);
        return u;
    }

    /**
     * 刷新过期时间
     *
     * @return res
     */
    @Override
    public boolean flushToken() {
        LoginUser u = UserContext.getLoginUser();
        return stringRedisTemplate.expire(String.format(UserRedisKeyConstant.USER_LOGIN_KEY, u.getToken()), 7L, TimeUnit.DAYS);
    }

    /**
     * 修改用户信息
     *
     * @param user 用户信息
     * @return 修改后的用户信息
     */
    @Override
    public boolean updateUser(User user) {
        return userMapper.updateById(user) > 0;
    }

    /**
     * 注销用户
     *
     * @return res
     */
    @Override
    public boolean removeUser() {
        LoginUser u = UserContext.getLoginUser();
        return userMapper.deleteById(u.getUserId()) > 0;
    }

    /**
     * 用户注册
     *
     * @param user 用户信息
     * @return 注册后的用户信息
     */
    @Override
    public UserResponse registerUser(User user) {
        //先判断一下这个手机号码是否在规定时间内验证过这里假设已经验证过了过期时间为30s那么在这个时间内是可以获取到数据的
        String res = stringRedisTemplate.opsForValue().get(String.format(UserRedisKeyConstant.USER_SMS_KEY, user.getPhone()));
        if (null == res) {
            throw new ServiceException(CodeMsg.REGISTER_ERROR);
        }
        if (userMapper.insert(user) > 0) {
            UserResponse u = new UserResponse();
            BeanUtils.copyProperties(user, u);
            return u;
        }
        throw new ServiceException(CodeMsg.REGISTER_ERROR);
    }

    /**
     * 关注行为
     *
     * @param userId   被关注的用户id
     * @param isFollow 是否关注
     * @return res
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean doConcern(Integer userId, boolean isFollow) {
        //获取一下关注的用户信息然后放入redis
        User u = userMapper.selectById(userId);
        if (null == u) {
            throw new ServiceException("请求异常!");
        }
        LoginUser u1 = UserContext.getLoginUser();
        if (isFollow) {
            if (concernService.doConcern(userId)) {
                stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
                    @Override
                    public <K, V> Object execute(@NonNull RedisOperations<K, V> operations) throws DataAccessException {
                        HashOperations<String, Integer, Object> ho = (HashOperations) operations.opsForHash();
                        //关注成功后往自己的关注列表和别人的粉丝列表里面添加数据
                        ho.put(String.format(UserRedisKeyConstant.USER_CONCERN_KEY, u1.getUserId()), userId, JSON.toJSONString(u));
                        //往其他人的粉丝列表里面添加自己
                        ho.put(String.format(UserRedisKeyConstant.USER_FANS_KEY, userId), u1.getUserId(), JSON.toJSONString(u1));
                        ZSetOperations<String, Integer> zo = (ZSetOperations<String, Integer>) operations.opsForZSet();
                        zo.add(String.format(UserRedisKeyConstant.USER_CONCERN_Z_KEY, u1.getUserId()), userId, userId);
                        zo.add(String.format(UserRedisKeyConstant.USER_FANS_Z_KEY, userId), u1.getUserId(), u1.getUserId());
                        // 返回null即可，因为返回值会被管道的返回值覆盖，外层取不到这里的返回值
                        return null;
                    }
                });
                return true;
            }
            return false;
        }
        //取消关注
        if (concernService.cancelConcern(userId)) {
            stringRedisTemplate.executePipelined(new SessionCallback<Object>() {
                @Override
                public <K, V> Object execute(@NonNull RedisOperations<K, V> operations) throws DataAccessException {
                    HashOperations<String, Integer, Object> ho = (HashOperations) operations.opsForHash();
                    //删除
                    ho.delete(String.format(UserRedisKeyConstant.USER_CONCERN_KEY, u1.getUserId()), userId);
                    ho.delete(String.format(UserRedisKeyConstant.USER_FANS_KEY, userId), u1.getUserId());
                    ZSetOperations<String, Integer> zo = (ZSetOperations<String, Integer>) operations.opsForZSet();
                    zo.remove(String.format(UserRedisKeyConstant.USER_CONCERN_Z_KEY, u1.getUserId()), userId);
                    zo.remove(String.format(UserRedisKeyConstant.USER_FANS_Z_KEY, userId), u1.getUserId());
                    // 返回null即可，因为返回值会被管道的返回值覆盖，外层取不到这里的返回值
                    return null;
                }
            });
            return true;
        }
        return false;
    }

    /**
     * 关注用户计数
     *
     * @param userId 用户id
     * @return res
     */
    @Override
    public Long concernCount(Integer userId) {
        return concernService.concernCount(userId);
    }

    /**
     * 粉丝计数
     *
     * @param userId 用户id
     * @return 结果
     */
    @Override
    public Long fansCount(Integer userId) {
        return concernService.fansCount(userId);
    }

    /**
     * 粉丝列表
     *
     * @param index 索引
     * @param limit 取多少
     * @return res
     */
    @Override
    public List<User> fansList(Integer index, Integer limit) {
        Integer userId = UserContext.getLoginUser().getUserId();
        int start = (index - 1) * limit;
        int end = index * limit;
        //先获取到键，然后再去查需要返回的数据
        Set<String> set = Optional
                .ofNullable(stringRedisTemplate
                        .opsForZSet()
                        .range(String.format(UserRedisKeyConstant.USER_FANS_Z_KEY, userId), start, end))
                .orElseThrow(() -> new ServiceException("获取粉丝列表失败!"));
        List<User> fansList = new ArrayList<>();
        for (String s : set) {
            User u = (User) stringRedisTemplate.opsForHash().get(String.format(UserRedisKeyConstant.USER_FANS_KEY, userId), s);
            fansList.add(u);
        }
        return fansList;
    }

    /**
     * 关注列表
     *
     * @param index 索引
     * @param limit 取多少
     * @return res
     */
    @Override
    public List<User> concernList(Integer index, Integer limit) {
        Integer userId = UserContext.getLoginUser().getUserId();
        int start = (index - 1) * limit;
        int end = index * limit;
        //先获取到键，然后再去查需要返回的数据
        Set<String> set = Optional
                .ofNullable(stringRedisTemplate
                        .opsForZSet()
                        .range(String.format(UserRedisKeyConstant.USER_CONCERN_Z_KEY, userId), start, end))
                .orElseThrow(() -> new ServiceException("获取关注列表失败!"));
        List<User> concernList = new ArrayList<>();
        for (String s : set) {
            User u = (User) stringRedisTemplate.opsForHash().get(String.format(UserRedisKeyConstant.USER_CONCERN_KEY, userId), s);
            concernList.add(u);
        }
        return concernList;
    }


}
